﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Generator
{
    /// <summary>
    /// this static class is responsible for calculating penalties for solutions
    /// </summary>
    /// <remarks>
    /// this PART of the class contains "low level" parameters, retrieved 
    /// in run-Time from the pParameters file.
    /// </remarks>
    public static partial class Penalties
    {
        /* This region contains "low level" pParameters, retrieved from the pParameters file.
         * Although being stored in a pParameters file allows for modifying them (without
         * recompiling the whole application), I've only done it for the sake of principle.
         * The average user is not meant to tweak those pParameters. */

        #region fixed, "low level" parameters

        /* The parameters below affect the way the engine penalizes solutions for
         * overtime and "undertime" (eg. someone On a 30 hrs _contract works 39 hours,
         * or 17 hours).
         * My application uses a sophisticated system, employing fractional power
         * functions.
         * This ensures that - say - if, having two emps_from_database each On a 30 hrs _contract -
         * one of them gets 22 hours of overtime (and the other one None), this would be
         * penalized more heavily than giving 11 hours of overtime to both of them.
         * 22 hours of overtime generate 22^1.6 = 140.6 penalty points
         * while 11 hours of overtime 11^1.6 = 46.4
         * (46.4*2 = 92.8 < 140.6).
         * 
         * That'pShift how the engine enforces spreading the working Time evenly between emps_from_database.
         * 
         * There'pShift an additional twist to it called the "tolerance zone".
         * Within the tolerance zone (by default - up to 10 hours of overtime / undertime)
         * smaller exponent is used. */

        /// <summary>
        /// the exponent used to calculate the penalty for exceeding _employee'pShift _contract hours
        /// </summary>
        /// <remarks>if the overtime is smaller than overtimeTolerance, tolerantOvertimeExponent is applied instead.</remarks>
        public static readonly double overtimeExponent;
        /// <summary>
        /// the exponent used to calculate the penalty for not reaching the _employee'pShift _contract hours
        /// </summary>
        /// <remarks>if the "undertime" is smaller than defined by undertimeTolerance, tolerantUndertimeExponent is applied instead.</remarks>
        public static readonly double undertimeExponent;
        /// <summary>
        /// the exponent used to calculate the penalty for exceeding _employee'pShift _contract hours within the "tolerance zone"
        /// </summary>
        /// <remarks>
        /// The tolerance zone (or how much overtime you can give for tolerantOvertimeExponent still to be applied)
        /// is defined by overtimeTolerance variable.
        /// </remarks>
        public static readonly double tolerantOvertimeExponent;
        /// <summary>
        /// the exponent used to calculate the penalty for not reaching the _employee'pShift _contract hours (but within the "tolerance zone")
        /// </summary>
        /// <remarks>
        /// The tolerance zone (or how much "undertime" there can be for tolerantUndertimeExponent still to be applied)
        /// is defined by undertimeTolerance variable.
        /// </remarks>
        public static readonly double tolerantUndertimeExponent;
        /// <summary>
        /// defines the range within wich tolerantOvertimeExponent is still applied for calculating the overtime penalty
        /// (and not the overtimeExponent)
        /// </summary>
        public static readonly int overtimeTolerance;
        /// <summary>
        /// defines the range within wich tolerantUndertimeExponent is still applied for calculating the "undertime" penalty
        /// (and not the undertimeExponent)
        /// </summary>
        public static readonly int undertimeTolerance;


        /// <summary>
        /// fixed penalty applied for every case When an _employee does 2 different Shifts On the same Day ('revisits' the store)
        /// </summary>
        public static readonly int revisitPenalty;

        /* ===========================================================
         * NOTE: the overtime/undertime penalties are always applied.
         * The user cannot disable them.
         * The ones below are optional and are applied accordingly to the Parameters
         * (set by the user in the GeneratingRota window). */

        /// <summary>
        /// the base weight of the penalty applied for getting people to work the entire weekend
        /// </summary>
        public static readonly int weekendPenaltyWeight;

        #region overday penalty
        /// <summary>
        /// the base weight of the penalty applied for getting people to work more bits than they should
        /// </summary>
        /// <remarks>
        /// how many bits they *should* work is defined by Penalties.preferredNumberOfWorkingDays method.
        /// by default, 20 hours is calculated to 2 working bits, 30 hours = 3 bits, 48 hours = 5 bits.
        /// </remarks>
        public static readonly int overdaysPenaltyWeight;

        /* overdaysPenalty_fractional... values refer to how the overdays penalty is being applied.
         * let'pShift say someone'pShift "optimal" number of working bits is 3, but he is given 6 bits in the schedule.
         * 3 bits too much.
         * the penalty will be:
         * fractionalWeightBASE + (fractionalWeightBASE + fractionalWeightEXTRA)
         * + (fractionalWeightBASE + 2*fractionalWeightEXTRA) =
         * [by default values] 0.5 + (0.5+0.3)+(0.5+0.3+0.3) = 2.4
         * now this 2.4 basic number will be multiplied by the base Value -
         * overdaysPenaltyWeight (see above).
         * by default it'pShift 200, so, 2.4 * 200 = 480.
         * 
         * SIXDAYS and WHOLEWEEK penalties are applied additionally,
         * following the idea that nobody should be forced to work 6 or 7 bits a week.
         * 
         * Thez are not added though - the result (as 2.4 in the above example)
         * is multiplied by them. As WHOLEWEEK is - by default - 2.0, the result would
         * be doubled.
         * This ensures that solutions which get an _employee to not have any bits off
         * are penalized heavily. */
        /// <summary>
        /// added for every working Day above the optimal number. jump to definition for detailed explanations (in comments)
        /// </summary>        
        public static readonly double overdaysPenalty_fractionalWeightBASE;
        /// <summary>
        /// added n times for every n-th working Day above the optimal number. jump to definition for detailed explanations (in comments)
        /// </summary>        
        public static readonly double overdaysPenalty_fractionalWeightEXTRA;
        /// <summary>
        /// the fractionalWeight overall result gets multiplied by this Value if the _employee is gotten to work 6 bits a week. jump to definition for detailed explanations (in comments)
        /// </summary>        
        public static readonly double overdaysPenalty_fractionalWeightSIXDAYS;
        /// <summary>
        /// the fractionalWeight overall result gets multiplied by this Value if the _employee is not given any bits off in the week. jump to definition for detailed explanations (in comments)
        /// </summary>        
        public static readonly double overdaysPenalty_fractionalWeightWHOLEWEEK;
        #endregion

        /* the penalties grouped in arrays are supposed to contain increasing numbers.
         * if the perfect keyflow is 7, then if we achieve 5 -
         * keyflowPenalties[2] will be applied.
         * 
         * if the maximum number of full Shifts in a row is 2 -
         * then if somebody get'pShift 3 Shifts in a row, fullshiftsPenalties[1] applies. 
         * 
         * NOTE: since arrays cannot be stored in pParameters files, they are actually string values
         * (numbers separated with commas) which get parsed in the constructor. */

        /// <summary>
        /// penalties for failing to achieve the perfect keyflow
        /// </summary>
        public static readonly int[] keyflowPenalties;
        /// <summary>
        /// penalties for getting too many full (Open-Close) Shifts in a row
        /// </summary>
        public static readonly int[] fullshiftPenalties;

        /// <summary>
        /// retrieves the read-only low-level parameters from the pParameters file
        /// </summary>
        static Penalties()
        {
            overtimeExponent = Settings.Default.PENALTIES_OvertimeExponent;
            undertimeExponent = Settings.Default.PENALTIES_UndertimeExponent;
            tolerantOvertimeExponent = Settings.Default.PENALTIES_TolerantOvertimeExponent;
            tolerantUndertimeExponent = Settings.Default.PENALTIES_TolerantUndertimeExponent;
            overtimeTolerance = Settings.Default.PENALTIES_OvertimeToleranceZone;
            undertimeTolerance = Settings.Default.PENALTIES_UndertimeToleranceZone;
            weekendPenaltyWeight = Settings.Default.WEIGHT_Weekend;
            overdaysPenaltyWeight = Settings.Default.WEIGHT_Overdays;
            overdaysPenalty_fractionalWeightBASE = Settings.Default.WEIGHT_OverdaysFraction;
            overdaysPenalty_fractionalWeightEXTRA = Settings.Default.WEIGHT_OverdaysFractionExpotential;
            overdaysPenalty_fractionalWeightSIXDAYS = Settings.Default.WEIGHT_OverdaysSixDays;
            overdaysPenalty_fractionalWeightWHOLEWEEK = Settings.Default.WEIGHT_OverdaysFullWeek;
            keyflowPenalties = Settings.Default.PENALTIES_KeyFlow.Split(',').Select(s => Int32.Parse(s)).ToArray();
            fullshiftPenalties = Settings.Default.PENALTIES_FullShift.Split(',').Select(s => Int32.Parse(s)).ToArray();
            revisitPenalty = Settings.Default.PENALTIES_RevisitPenalty;

            bool overtimeNonsense = tolerantOvertimeExponent > overtimeExponent;
            bool undertimeNonsense = tolerantUndertimeExponent > undertimeExponent;

            if (overtimeNonsense | undertimeNonsense)
            {
                string message = "The parameters in the Settings files have been tweaked in a nonsensical way:";
                message += Environment.NewLine;
                if (overtimeNonsense)
                {
                    message += "ENGINEPENALTIES_tolerantOvertimeExponent is greater than ENGINEPENALTIES_overtimeExponent.";
                    if(undertimeNonsense) message+=" and ";
                }
                if (undertimeNonsense)
                    message += "ENGINEPENALTIES_tolerantUndertimeExponent is greater than ENGINEPENALTIES_undertimeExponent.";
                message += Environment.NewLine;

                message += "You can still use the schedule generating feature, but it may work erratically";                
                message += " in the sense of producing suboptimal solutions.";
                message += Environment.NewLine;
                message += "It is recommended to fix the settings. Please notify the system administrator.";
                
                MessageBox.Show(message, "Nonsensical settings", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }

        }


        #endregion
    } 



        
}
